#include <stdlib.h>
#include <string.h>
#include "mwave.h"

#include "Huffman.h"

#ifdef WINDOWS_MAKE
#include <stdio.h>

DataLocation LoadIntoRAM(const char* filename)
{
	DataLocation loc;
	long file_len;
	char* binary;
	FILE *fd = fopen(filename, "rb");
	int x=0;

	loc.size = 0;
	if (!fd) return loc;
	fseek(fd, 0, SEEK_END);
	file_len = ftell(fd);
	binary = (char*)malloc(sizeof(char) * file_len);
	fseek(fd, 0, SEEK_SET);
	fread(binary, file_len, sizeof(char), fd);
	loc.ptr = binary;
	loc.size = file_len;

	fclose(fd);
	return loc;
}

void SaveIntoDrive(DataLocation loc, const char* filename)
{
	char* binary = (char*)loc.ptr;
	FILE *fd = fopen(filename, "wb");

	if (!fd)
	{
		free(binary);
		return;
	}
	fwrite(binary, loc.size, sizeof(char), fd);

	fclose(fd);
	free(binary);
	loc.size = 0;
	return;
}
#endif

WaveHeader ReadWaveHeader(DataLocation loc)
{
	unsigned char* ptr = (unsigned char*)loc.ptr;
	long idx = 0;
	int tint;
	short tshort;
	WaveHeader wavhdr;

	tint = loc.size - 8;
	if (memcmp(ptr + idx, "RIFF", 4) == 0 
		&& memcmp(ptr + idx + 4, &tint, 4) == 0 /* compare wave file size */
		&& memcmp(ptr + idx + 8, "WAVE", 4) == 0
		&& memcmp(ptr + idx + 12, "fmt ", 4) == 0)
	{
		idx += 20; // + sub chunk1 header size
		tshort = 0x0001;
		if (memcmp(ptr + idx, &tshort, 2) == 0)
		{
			idx += 2;
			memcpy(&wavhdr.nChannels, ptr + idx, 2);
			idx += 2;
			memcpy(&wavhdr.vSampleRates, ptr + idx,4); // sample rate
			idx += 10; // + pass byte rates + block align
			memcpy(&wavhdr.vBps, ptr + idx, 2);
			idx += 2;
			// Find ETP, DEP
			wavhdr.HTP = wavhdr.ETP = wavhdr.DEP = idx;
			while(idx < loc.size)
			{				
				if (memcmp("HUFF", ptr + idx, 4) == 0)
					wavhdr.HTP = idx;
				if (memcmp("ENTR", ptr + idx, 4) == 0)
					wavhdr.ETP = idx;
				if (memcmp("data", ptr + idx, 4) == 0)
				{
					wavhdr.DEP = idx;
					break; // because 'data' section aligned on the last
				} 
				idx += 4;
			}
		}
	}
	return wavhdr;
}

#ifdef WINDOWS_MAKE
void WriteWaveHeader(DataLocation* loc, WaveHeader wavhdr)
{
	unsigned char* ptr;
	int tint;
	short tshort;
	long idx = 0;

	loc->size = 44;
	ptr = (unsigned char*) (loc->ptr = malloc(loc->size));

	// chunk head - 12 bytes
	memcpy(ptr + idx, "RIFF", 4);
	idx += 4;
	tint = loc->size;
	memcpy(ptr + idx, &tint, 4);
	idx += 4;
	memcpy(ptr + idx, "WAVE", 4);
	// sub chunk1 header - 8 bytes
	idx += 4;
	memcpy(ptr + idx, "fmt ", 4);
	idx += 4;
	tint = 16; // sub chunk1 header size
	memcpy(ptr + idx, &tint, 4);
	idx += 4;
	// sub chunk1 data - 16 bytes
	tshort = 0x0001;
	memcpy(ptr + idx, &tshort, 2);
	idx += 2;
	memcpy(ptr + idx, &wavhdr.nChannels, 2);
	idx += 2;
	memcpy(ptr + idx, &wavhdr.vSampleRates, 4); // sample rate
	idx += 4;
	tint = wavhdr.vSampleRates * wavhdr.nChannels * (wavhdr.vBps / 8); // byte rate
	memcpy(ptr + idx, &tint, 4);
	idx += 4;
	tshort = wavhdr.nChannels * (wavhdr.vBps / 8); // block align
	memcpy(ptr + idx, &tshort, 2);
	idx += 2;
	memcpy(ptr + idx, &wavhdr.vBps, 2);
	idx += 2;
	// Write HTP
	memcpy(ptr + idx, "HUFF", 4);
	idx += 4;
	if (ISVALIDPTR(wavhdr.pTree)) tint = wavhdr.pTree->count;
	else tint = 0;
	memcpy(ptr + idx, &wavhdr.pTree->count, 4);
	idx += 4;
	// Write ETP
	memcpy(ptr + idx, "ENTR", 4);
	idx += 4;
	tint = 0;
	memcpy(ptr + idx, &tint, 4);	
}

void InsertWaveEntryTable(DataLocation* loc, WaveHeader* wavhdr, int data_offset)
{
	unsigned char* ptr = (unsigned char*)loc->ptr;
	int tint;
	short idx = wavhdr->ETP;
	if (memcmp(ptr + idx, "ENTR", 4) == 0) 
	{
		idx += 4;
		memcpy(&tint, ptr + idx, 4);
		tint += 4;
		memcpy(ptr + idx, &tint, 4); // update wave entry table size
		idx += tint; // move to end of wave entry table
		loc->size += 4;
		loc->ptr = realloc(loc->ptr, loc->size);
		memmove(ptr + idx + 4, ptr + idx, loc->size - idx - 4); // push behind data
		memcpy(ptr + idx, &data_offset, 4); // insert data_offset
		wavhdr->DEP += 4; // update DEP
		// update wave size
		tint = loc->size - 8;
		memcpy(ptr + 4, &tint, 4);
	}
}
#endif

static void GetDataEntryPoint(unsigned char *binary_wav_ptr, short entry_table_point, short data_entry_point, int nth, long* nth_data_entry_point)
{
	long toffset = entry_table_point;
	if (memcmp(binary_wav_ptr + toffset, "ENTR", 4) == 0)
	{
		toffset += 4 + nth * 4; // get the offset of N-th data entry point
		memcpy(&toffset, binary_wav_ptr + toffset, 4); // read data offset
	}
	else if (nth == 0)
	{
		toffset = 0;
	}
	(*nth_data_entry_point) = data_entry_point + toffset; // point to N-th data entry
}

WaveData ReadWaveData(DataLocation loc, WaveHeader wavhdr, int nth)
{
	unsigned char* ptr = (unsigned char*)loc.ptr;
	long idx = wavhdr.ETP;
	long nth_DEP;
	WaveData wavdat;
	
	GetDataEntryPoint(ptr, wavhdr.ETP, wavhdr.DEP, nth, &nth_DEP);
	ptr += (wavdat.DEP = nth_DEP);

	idx = 0;
	if (memcmp(ptr, "data", 4) == 0)
	{
		idx += 4;
		memcpy(&wavdat.size, ptr + idx, 4); // read data size
		idx += 4;
		wavdat.data = (unsigned char*)malloc(wavdat.size);
		memcpy(wavdat.data, ptr + idx, wavdat.size); // read data
	}		

	return wavdat;
}

#ifdef WINDOWS_MAKE
void WriteWaveData(DataLocation* loc, WaveHeader* wavhdr, WaveData wavdat)
{
	unsigned char* ptr;
	long idx;
	int tint;
	short tshort;

	// update wave entry table 
	InsertWaveEntryTable(loc, wavhdr, loc->size - wavhdr->DEP);
	// write wave data 
	idx = loc->size; // it should be located after InsertWaveEntryTable
	loc->size += wavdat.size + 8;
	ptr = (unsigned char*)(loc->ptr = realloc(loc->ptr, loc->size));
	memcpy(ptr + idx, "data", 4);
	idx += 4;
	memcpy(ptr + idx, &wavdat.size, 4); // data length
	idx += 4;
	memcpy(ptr + idx, wavdat.data, wavdat.size); // data
	// update wave size
	tint = loc->size - 8;
	memcpy(ptr + 4, &tint, 4);
}
#endif

HuffmanTree ReadHuffmanTree(DataLocation loc, WaveHeader wavhdr)
{
	HuffmanTree huffs;

#ifdef WINDOWS_MAKE // read from the drive
	unsigned* ptr = (unsigned*)loc.ptr;
	short idx = wavhdr.HTP;
#else
	unsigned* ptr = ReadHuffmanTreeFromMemory();
	short idx = 0;
#endif

	int tint;

	if (memcmp(ptr + idx, "HUFF", 4) == 0)
	{
		idx += 4;
		memcpy(&huffs.count, ptr + idx, 4);
		idx += 4;

		// read huffman tree 
//#ifdef WINDOWS_MAKE 
//		huffs.nodes = (HuffmanNode**)malloc(sizeof(HuffmanNode*) * huffs.count);
//		for(tint = 0;tint < huffs.count; tint)
//		{
//			huffs.nodes[tint] = (HuffmanNode*)malloc(sizeof(HuffmanNode));
//			memcpy(huffs.nodes[tint], ptr + idx, 3);
//			idx += 3;
//		}
//#else
		huffs.nodes = (HuffmanNode**)(ptr + idx);
//#endif
	}
	return huffs;
}

#ifdef WINDOWS_MAKE
void WriteHuffmanTree(DataLocation* loc, WaveHeader* wavhdr, HuffmanTree huffs)
{
	unsigned char* ptr = (unsigned char*)loc->ptr;
	short idx = wavhdr->ETP;
	int tint;
	int sHuffmanNode;

	if (memcmp(ptr + idx, "ENTR", 4) == 0) 
	{
		wavhdr->HTP = idx; // update HTP
		// re-allocating location pointer
		sHuffmanNode = sizeof(HuffmanNode) * 3 / 4;
		loc->size += sHuffmanNode * huffs.count + 4 + 2; // {(sizeof(HuffmanNode) = 8) * 3 / 4} * numofhuff + HUFF(4 bytes) + length(2 bytes)
		loc->ptr = realloc(loc->ptr, loc->size);
		memmove(ptr + idx + sHuffmanNode * huffs.count + 6, ptr + idx, loc->size - (idx + 4)); // push behind data
		// write huffman tree
		memcpy(ptr + idx, "HUFF", 4);
		idx += 4;
		memcpy(ptr + idx, &huffs.count, 2);
		idx += 2;
		for(tint = 0;tint < huffs.count;tint++)
		{
			memcpy(ptr + idx, huffs.nodes[tint], sHuffmanNode);
			free(huffs.nodes[tint]); // write & free
			idx += sHuffmanNode;
		}
		free(huffs.nodes); // free all
		// update ETP, DEP
		wavhdr->ETP += sHuffmanNode * huffs.count + 6;
		wavhdr->DEP += sHuffmanNode * huffs.count + 6;
		// update wave size
		tint = loc->size - 8;
		memcpy(ptr + 4, &tint, 4);
	}
}
#endif

void LoadWaveData(DataLocation loc, WaveHeader wavhdr, int nth, unsigned char* buf, int buf_size, int* buf_lock)
{
	unsigned char* ptr = (unsigned char*)loc.ptr;
	long writing_wav_size, nth_wav_size;
	long nth_DEP;
	long tsize;	
	int i;
	BYTE Des = 0; // assign difference as 0

	GetDataEntryPoint(ptr, wavhdr.ETP, wavhdr.DEP, nth, &nth_DEP);
	ptr += nth_DEP; // N-th data entry point ptr

	if (memcmp(ptr, "data", 4) == 0)
	{
		ptr += 4;
		memcpy(&nth_wav_size, ptr, 4); // read data size
		nth_wav_size = nth_wav_size << 3; // total compressed 'bits'
		ptr += 4;
		writing_wav_size = 0;
		while(writing_wav_size < nth_wav_size) // fill sound buffer
		{
			if (!(*buf_lock))
			{
				// Fill sound buffer as decompressed wave data
				memset(buf, 0x0, buf_size);
				tsize = DecompressHuffmanPartially(buf, ptr, writing_wav_size, nth_wav_size, buf_size);
				// Recovery differences
				buf[0] = buf[0] + Des;
				for(i = 1; i < buf_size; i++)
				{
					buf[i] = buf[i] + buf[i-1];
				}
				Des = buf[buf_size-1];
				writing_wav_size += tsize; // 'tsize' is the decompressed bits among compressed bits
				(*buf_lock) = 1; // getting buf_lock
			}
		}
	}
}